#include <iostream>
#include <thread>
#include <memory>
#include "../test1.service.Service.proxy.h"
#include "../test1.service.ServiceDynamic.proxy.h"
#include "../rpc_root.h"
#include "unittest.hh"

// Simulates a user implemented Transport interface
class TransportImpl : public rpc::Transport {
    char* data_ {nullptr};
    int begin_ {0};
    int end_ {0};
    bool close_ {false};

public:
    TransportImpl() {
        data_ = new char[1024 * 16];
    }

    virtual ~TransportImpl() {
        delete[] data_;
    }

    virtual int send(const char* data, int size) override {
        begin_ = 0;
        end_ = 0;
        memcpy(data_ + begin_, data, size);
        end_ += size;
        return size;
    }
    virtual int peek(char* data, int size) override {
        memcpy(data, data_ + begin_, size);
        return size;
    }
    virtual int recv(char* data, int size) override {
        memcpy(data, data_ + begin_, size);
        begin_ += size;
        if (begin_ == end_) {
            begin_ = 0;
            end_ = 0;
        }
        return size;
    }
    virtual int skip(int size) override {
        begin_ += size;
        return size;
    }
    virtual int size() override {
        return end_ - begin_;
    }
    virtual bool isClose() override {
        return close_;
    }
    virtual void close() override {
        close_ = true;
    }
};

FIXTURE_BEGIN(RPC)

CASE(TestVersion1) {
    rpc::RpcVersion v("1.0.1");
    ASSERT_TRUE(v[0] == "1" && v[1] == "0" && v[2] == "1");
}

CASE(TestVersion2) {
    rpc::RpcVersion v1("1.1.0");
    rpc::RpcVersion v2("1.1.1");
    ASSERT_TRUE(v1 < v2);
}

CASE(TestVersion3) {
    rpc::RpcVersion v1("1.0.1");
    rpc::RpcVersion v2("1.0.1");
    ASSERT_FALSE(v2 < v1);
}

CASE(TestVersion4) {
    rpc::RpcVersion v1("1.0.1");
    rpc::RpcVersion v2("1.0.2");
    ASSERT_FALSE(v2 < v1);
}

CASE(TestOnewayMethod) {
    auto trans = std::make_shared<TransportImpl>();
    auto service = rpc::getService<ServiceProxy>(trans);
    // Call proxy method
    service->method1({}, "123");
    // Simulates remote service received a message
    RpcRef.onMessage(trans);
    // Resume service's coroutine 
    RpcRef.update();
}

CASE(TestCoroutineProxyMethod) {
    auto trans = std::make_shared<TransportImpl>();
    auto service = rpc::getService<ServiceProxy>(trans);
    bool flag = false;
    co (
        auto ret = service->method2(1, {"2", "3"}, 4);
        flag = (ret == "hello");
    );
    // Simulates remote service received a message
    RpcRef.onMessage(trans);
    RpcRef.update();
    // Simulates remote service's returned message
    RpcRef.onMessage(trans);
    ASSERT_TRUE(flag);
}

CASE(TestCoroutineProxyMethodAsync) {
    auto trans = std::make_shared<TransportImpl>();
    auto service = rpc::getService<ServiceProxy>(trans);
    bool flag = false;
    service->method2(1, {"2", "3"}, 4, [&](const std::string& ret, rpc::RpcError){
        flag = (ret == "hello");
    });
    // Simulates remote service received a message
    RpcRef.onMessage(trans);
    RpcRef.update();
    // Simulates remote service's returned message
    RpcRef.onMessage(trans);
    ASSERT_TRUE(flag);
}

CASE(TestProxyMethodException1) {
    auto trans = std::make_shared<TransportImpl>();
    auto service = rpc::getService<ServiceProxy>(trans);
    bool flag = false;
    service->method4([&](rpc::RpcError error){
        flag = (error == rpc::RpcError::EXCEPTION);;
    });
    // Simulates remote service received a message
    RpcRef.onMessage(trans);
    RpcRef.update();
    // Simulates remote service's returned message
    RpcRef.onMessage(trans);
    ASSERT_TRUE(flag);
}

CASE(TestProxyMethodException2) {
    auto trans = std::make_shared<TransportImpl>();
    auto service = rpc::getService<ServiceProxy>(trans);
    bool flag = false;
    co (
        try {
            service->method4();
        } catch (rpc::RemoteMethodException&) {
            flag = true;
        }
    )
    // Simulates remote service received a message
    RpcRef.onMessage(trans);
    RpcRef.update();
    // Simulates remote service's returned message
    RpcRef.onMessage(trans);
    ASSERT_TRUE(flag);
}

CASE(TestProxyMethodTimeout) {
    auto trans = std::make_shared<TransportImpl>();
    auto service = rpc::getService<ServiceProxy>(trans);
    bool flag = false;
    co (
        try {
            service->method4();
        } catch (rpc::RemoteMethodTimeout&) {
            flag = true;
        }
    )
    std::this_thread::sleep_for(std::chrono::seconds(6));
    // Simulates remote service received a message
    RpcRef.update();
    ASSERT_TRUE(flag);
}

CASE(TestProxyMethodMainCoroutine) {
    auto trans = std::make_shared<TransportImpl>();
    auto service = rpc::getService<ServiceProxy>(trans);
    bool flag = false;
    try {
        service->method5();
    } catch (rpc::ProxyCoroMethodException&) {
        flag = true;
    }
    ASSERT_TRUE(flag);
}

CASE(TestOnewayMethodDynamic) {
    auto trans = std::make_shared<TransportImpl>();
    rpc::loadClass("9046620621990182747", "dynamic.so");
    auto service = rpc::getService<ServiceDynamicProxy>(trans);
    // Call proxy method
    service->method1({}, "123");
    // Simulates remote service received a message
    RpcRef.onMessage(trans);
    // Resume service's coroutine 
    RpcRef.update();
}

CASE(TestCoroutineProxyMethodDynamic) {
    auto trans = std::make_shared<TransportImpl>();
    auto service = rpc::getService<ServiceDynamicProxy>(trans);
    bool flag = false;
    co (
        auto ret = service->method2(1, {"2", "3"}, 4);
        flag = (ret == "hello");
    );
    // Simulates remote service received a message
    RpcRef.onMessage(trans);
    RpcRef.update();
    // Simulates remote service's returned message
    RpcRef.onMessage(trans);
    ASSERT_TRUE(flag);
}

CASE(TestCoroutineProxyMethodAsyncDynamic) {
    auto trans = std::make_shared<TransportImpl>();
    auto service = rpc::getService<ServiceDynamicProxy>(trans);
    bool flag = false;
    service->method2(1, {"2", "3"}, 4, [&](const std::string& ret, rpc::RpcError error){
        flag = (ret == "hello") && (error == rpc::RpcError::OK);
    });
    // Simulates remote service received a message
    RpcRef.onMessage(trans);
    RpcRef.update();
    // Simulates remote service's returned message
    RpcRef.onMessage(trans);
    ASSERT_TRUE(flag);
}

CASE(TestProxyMethodExceptionDynamic1) {
    auto trans = std::make_shared<TransportImpl>();
    auto service = rpc::getService<ServiceDynamicProxy>(trans);
    bool flag = false;
    service->method4([&](rpc::RpcError error){
        flag = (error == rpc::RpcError::EXCEPTION);
    });
    // Simulates remote service received a message
    RpcRef.onMessage(trans);
    RpcRef.update();
    // Simulates remote service's returned message
    RpcRef.onMessage(trans);
    ASSERT_TRUE(flag);
}

CASE(TestProxyMethodExceptionDynamic2) {
    auto trans = std::make_shared<TransportImpl>();
    auto service = rpc::getService<ServiceDynamicProxy>(trans);
    bool flag = false;
    co (
        try {
            service->method4();
        } catch (rpc::RemoteMethodException&) {
            flag = true;
        }
    )
    // Simulates remote service received a message
    RpcRef.onMessage(trans);
    RpcRef.update();
    // Simulates remote service's returned message
    RpcRef.onMessage(trans);
    ASSERT_TRUE(flag);
}

CASE(TestProxyMethodTimeoutDynamic) {
    auto trans = std::make_shared<TransportImpl>();
    auto service = rpc::getService<ServiceDynamicProxy>(trans);
    bool flag = false;
    co (
        try {
            service->method4();
        } catch (rpc::RemoteMethodTimeout&) {
            flag = true;
        }
    )
    std::this_thread::sleep_for(std::chrono::seconds(6));
    // Simulates remote service received a message
    RpcRef.update();
    ASSERT_TRUE(flag);
}

CASE(TestProxyMethodMainCoroutineDynamic) {
    auto trans = std::make_shared<TransportImpl>();
    auto service = rpc::getService<ServiceDynamicProxy>(trans);
    bool flag = false;
    try {
        service->method5();
    } catch (rpc::ProxyCoroMethodException&) {
        flag = true;
    }
    ASSERT_TRUE(flag);
}

CASE(TestDynamicReload1) {
    auto trans = std::make_shared<TransportImpl>();
    rpc::loadClass("9046620621990182747", "dynamic.so");
    auto service = rpc::getService<ServiceDynamicProxy>(trans);
    bool flag1 = false;
    co (
        auto ret = service->method2(1, {"2", "3"}, 4);
        flag1 = (ret == "hello");
    );
    // Simulates remote service received a message
    RpcRef.onMessage(trans);
    RpcRef.update();
    // Simulates remote service's returned message
    RpcRef.onMessage(trans);
    rpc::loadClass("9046620621990182747", "dynamic1.so");
    // Trigger unload event
    RpcRef.update();
    bool flag2 = false;
    co (
        auto ret = service->method2(1, {"2", "3"}, 4);
        flag2 = (ret == "hello world");
    );
    // Simulates remote service received a message
    RpcRef.onMessage(trans);
    RpcRef.update();
    // Simulates remote service's returned message
    RpcRef.onMessage(trans);
    ASSERT_TRUE(flag1);
    ASSERT_TRUE(flag2);
}

CASE(TestDynamicReload2) {
    rpc::unloadClassUnsafe("9046620621990182747");
    auto trans = std::make_shared<TransportImpl>();
    rpc::loadClass("9046620621990182747", "dynamic.so");
    auto service = rpc::getService<ServiceDynamicProxy>(trans);
    bool flag1 = false;
    co (
        auto ret = service->method2(1, {"2", "3"}, 4);
        flag1 = (ret == "hello");
    );
    // Simulates remote service received a message
    RpcRef.onMessage(trans);
    RpcRef.update();
    // Simulates remote service's returned message
    RpcRef.onMessage(trans);
    // Upgrading failure
    rpc::loadClass("9046620621990182747", "dynamic2.so");
    // Trigger unload event
    RpcRef.update();
    bool flag2 = false;
    co (
        auto ret = service->method2(1, {"2", "3"}, 4);
        flag2 = (ret == "hello");
    );
    // Simulates remote service received a message
    RpcRef.onMessage(trans);
    RpcRef.update();
    // Simulates remote service's returned message
    RpcRef.onMessage(trans);
    ASSERT_TRUE(flag1);
    ASSERT_TRUE(flag2);
}

CASE(TestOnewayMethodDynamicNotLoad) {
    auto trans = std::make_shared<TransportImpl>();
    auto service = rpc::getService<ServiceDynamicProxy>(trans);
    // Call proxy method
    service->method1({}, "123");
    // Simulates remote service received a message
    RpcRef.onMessage(trans);
    // Resume service's coroutine 
    RpcRef.update();
}

CASE(TestOnewayMethodDynamicLoadWrongUUID) {
    auto trans = std::make_shared<TransportImpl>();
    rpc::loadClass("9046620621990182747", "dynamic3.so");
    auto service = rpc::getService<ServiceDynamicProxy>(trans);
    // Call proxy method
    service->method1({}, "123");
    // Simulates remote service received a message
    RpcRef.onMessage(trans);
    // Resume service's coroutine 
    RpcRef.update();
}

CASE(TestMethodRetry1) {
    rpc::unloadClassUnsafe("9046620621990182747");
    auto trans = std::make_shared<TransportImpl>();
    auto service = rpc::getService<ServiceDynamicProxy>(trans);
    // Call proxy method
    bool flag = false;
    co (
        try {
            service->method2(1, {}, 2);
        } catch (rpc::RemoteMethodTimeout&) {
            flag = true;
        }
    );
    std::this_thread::sleep_for(std::chrono::seconds(2));
    // retry
    RpcRef.update();
    ASSERT_TRUE(!flag);
    std::this_thread::sleep_for(std::chrono::seconds(2));
    // retry
    RpcRef.update();
    ASSERT_TRUE(!flag);
    std::this_thread::sleep_for(std::chrono::seconds(2));
    // retry
    RpcRef.update();
    ASSERT_TRUE(!flag);
    std::this_thread::sleep_for(std::chrono::seconds(2));
    RpcRef.update();
    ASSERT_TRUE(flag);
}

CASE(TestMethodRetry2) {
    auto trans = std::make_shared<TransportImpl>();
    auto service = rpc::getService<ServiceDynamicProxy>(trans);
    // Call proxy method
    bool flag = false;
    service->method2(1, {}, 2, [&](const std::string&, rpc::RpcError error) {
        flag = (error == rpc::RpcError::TIMEOUT);
    });
    std::this_thread::sleep_for(std::chrono::seconds(2));
    // retry
    RpcRef.update();
    ASSERT_TRUE(!flag);
    std::this_thread::sleep_for(std::chrono::seconds(2));
    // retry
    RpcRef.update();
    ASSERT_TRUE(!flag);
    std::this_thread::sleep_for(std::chrono::seconds(2));
    // retry
    RpcRef.update();
    ASSERT_TRUE(!flag);
    std::this_thread::sleep_for(std::chrono::seconds(2));
    RpcRef.update();
    ASSERT_TRUE(flag);
}

TEARDOWN([](){
    rpc::deinitialize();
})

FIXTURE_END(RPC)
